Skip to content

remove(messages-cache-breakpoint): superseded by canonical content identity — DRAFT, stacks on #272, review alongside it - #281

Draft
Gunther-Schulz wants to merge 8 commits into
cnighswonger:mainfrom
Gunther-Schulz:pr/retire-messages-cache-breakpoint
Draft

remove(messages-cache-breakpoint): superseded by canonical content identity — DRAFT, stacks on #272, review alongside it#281
Gunther-Schulz wants to merge 8 commits into
cnighswonger:mainfrom
Gunther-Schulz:pr/retire-messages-cache-breakpoint

Conversation

@Gunther-Schulz

Copy link
Copy Markdown
Contributor

DRAFT — blocked on #272, deliberately opened now so the plan is visible during that review (this branch stacks on #272; the replacement is present in it). Will mark ready when #272 merges. Whether to take this at all is genuinely your call — see the trade-off below.

Why remove it

  • Its #47098 rationale (protect the head against mutation) is covered by feat(insertion-normalization): pin volatile reminder blocks so mid-history rewrites stop busting the cache #272's canonical content identity, which recognises a message across re-serializations instead of pinning index 0.
  • It spends the 4th and last API breakpoint slot (Claude Code occupies three). Holding it isn't free even while the extension behaves: any future consumer of that slot silently starves. On our fork that starvation masked a defective mid-history rung scheme for a day — freeing the slot armed it, measured at 57 self-inflicted stability violations before the scheme itself was retired. The ordering rule this removal carries: retire anything breakpoint-dependent before freeing the slot.
  • Measured after removal on two full-session corpora through the real pipeline: 0 stability / 0 safety / 0 sequence violations, 4th slot deliberately empty.

The honest trade-off

messages-cache-breakpoint ships enabled; #272's replacement is gated off by default. Merging this as-is changes default behavior for users who don't enable the new extensions. Options, in our order of preference: (a) flip #272's gates to default-on at the same time, (b) take this removal only with a release note, (c) keep the extension but default it off. We run (a)-equivalent in production; the corpora above measured exactly that configuration.

🤖 Generated with Claude Code

@Gunther-Schulz Gunther-Schulz changed the title remove(messages-cache-breakpoint): superseded by canonical content identity remove(messages-cache-breakpoint): superseded by canonical content identity — DRAFT, stacks on #272, review alongside it Jul 29, 2026
@Gunther-Schulz
Gunther-Schulz force-pushed the pr/retire-messages-cache-breakpoint branch from 52ea721 to 7eb27a7 Compare July 30, 2026 04:27
@Gunther-Schulz

Copy link
Copy Markdown
Contributor Author

Rebased onto #272's new tip (c713d0e, the duplicate-suppression addition) — no changes to this PR's own commit; still stacks on #272, review alongside it.

🤖 Generated with Claude Code

Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Jul 30, 2026
…ls-less tree skips, not dies

The static harvest.mjs import defeated the file's own slice-portability
idiom: in trees carrying only the extension (upstream cnighswonger#272/cnighswonger#278/cnighswonger#281)
the whole file failed at module load, so the real-pair check never
reached the skip it was designed to hit. The reader now loads like
replayTools — dynamically, inside the test — and the fixture branch
requires it, so tools-less trees skip with COULD NOT VERIFY.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb
@Gunther-Schulz
Gunther-Schulz force-pushed the pr/retire-messages-cache-breakpoint branch from 7eb27a7 to b07c0dc Compare July 30, 2026 12:37
@Gunther-Schulz

Copy link
Copy Markdown
Contributor Author

Rebased onto the current #272 tip (force-with-lease; the retire commit rides on top unchanged as b07c0dc). Tests at the new base: 67 pass / 1 designed skip / 0 fail. Merge order dependency unchanged: this lands after #272.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb

Gunther-Schulz and others added 8 commits August 1, 2026 15:23
…story rewrites stop busting the cache

Claude Code re-serializes <system-reminder> hook blocks inside
otherwise-stable user messages later in the session — moving one into
its own message or merging it into a neighbour — which edits history
mid-prefix and re-bills everything after the edit (reported
independently as anthropics/claude-code#76606; measured here as the
splice/insert-mid class, ~40 kB re-billed per unmitigated hit on a
real session).

The extension keeps a per-conversation canonical model of the message
history keyed by content identity (message-hash.mjs: content hash +
occurrence ordinal — position-independent, so repeated identical
reminders stay distinct). Incoming volatile blocks are pinned to their
first-seen serialization: when CC re-shapes an old reminder, the
forwarded bytes keep the canonical form and the prefix survives. A
history that stops matching the model (compaction, true rewrites)
resets honestly rather than forcing a stale canon — pins survive the
reset, order assumptions do not.

Gated off by default: CACHE_FIX_INSERTION_NORMALIZE=1 enables
normalization, CACHE_FIX_VOLATILE_PIN=1 the pinning. State persists
under the state dir and survives proxy restarts.

Measured on live traffic (513-request session, 2026-07-28): every
observed splice/insert-mid pair forwarded with 0 re-billed bytes; the
canonical-order invariant, cross-request stability and sequence gates
all report 0 violations over 2.5 GB of captures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lete the pin (anthropics/claude-code#76606)

When CC migrates a hook reminder out of its tool_result into a standalone
system message mid-history, the pin restores the first-seen inline form —
but the migrated copy still forwarded, splicing the same content in twice
(measured live: ~61 kB splice, 124k tokens re-billed on one turn). Now a
standalone message whose wrapper-normalized bytes equal a live pinned
block is suppressed: never forwarded, never given a canonical identity.
Genuine changes (normalized bytes differ) still forward and reset per the
existing rule; assistant-role messages are excluded on principle.
Suppression is re-detected each request from the pin set — no new state
file. One event line per suppression rides the insertion event log.

The real-pair red-green check in the new test file needs the replay
tooling and capture; in this slice it skips, and runs where the tools
land (cnighswonger#276).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZxGrF1LRBvmb7cFXmS2DH
…ture (slice of fork 2dfe0f0)

Path-scoped slice of fork commit 2dfe0f0: only the
insertion-suppression real-pair test and the pinned fixture it falls
back to. The same commit's harvest-pin.test.mjs and
mitigation-output-form.test.mjs changes belong to the verification-tools
slice (cnighswonger#276) and are not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
…pinned blocks — the 587k's shape

CC sometimes migrates ALL of a message's volatile blocks out together,
joined into one standalone message (both hook reminders, wrapper-stripped,
joined with "\n\n"), rather than one standalone per block. The existing
single-block suppression set could never match that shape. Each pinned
entry with >=2 volatile blocks now also registers a join-hash — its
blocks' unwrapped texts, in wire order, joined with the one observed
separator — and findSuppressibleDuplicate checks it as a second pass.
No subset-merges, no speculative separators: only the one shape measured
live (capture s-633915a8, msg863/864, and independently confirmed on a
second real occurrence at msg640/641 the same session).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 78940a0)
…cate is payload (insertion slice of fork e0f8fcb)

Path-scoped slice of fork commit e0f8fcb: the insertion-normalization
tail guard and its tests. The same commit's output-guard
assistant-terminal invariant (proxy/extensions/output-guard.mjs,
test/output-guard.test.mjs) belongs to the output-guard slice (cnighswonger#278) and
is not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
… fork da9bf8c

Makes the file slice-portable: without tools/ the real-pair check now
reaches its designed skip instead of dying at module load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb
…test reads (fork b1f7c58)

The merge-suppression test does a top-level read of this harvested,
sanitized fixture (16KB, no addresses); without it the file dies at
load. The fork-only exclusion list names only LEDGER-*.json — this
fixture is public on the fork and rides with its test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb
…entity

messages-cache-breakpoint pinned index 0 against head mutation
(CC#47098). insertion-normalization recognises a message across
re-serializations by content identity instead of pinning its position,
which covers that rationale without spending the 4th breakpoint slot —
CC occupies three of the API's four; this extension took the last.

Holding that slot is not free even when the extension behaves: any
future consumer of the 4th breakpoint silently starves, and on our
fork the starvation MASKED a defective mid-history rung scheme for a
full day — removing the breakpoint first, in isolation, armed it
(measured: 57 self-inflicted stability violations before the scheme
itself was retired). Hence one rule this removal carries: retire any
breakpoint-dependent scheme BEFORE freeing the slot.

Measured after removal on two full-session corpora, replaying the real
pipeline: 0 stability, 0 safety, 0 sequence violations; the 4th slot
stays deliberately empty. A stale reference comment in the ttl-tier
pipeline test goes with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 1, 2026
…t and verified; cnighswonger#276 scope discovery

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016y33RMV399iYMXFEbAfQCk
@Gunther-Schulz
Gunther-Schulz force-pushed the pr/retire-messages-cache-breakpoint branch from b07c0dc to fb63f61 Compare August 1, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant